﻿using System;
using System.Net;
using System.Threading;

namespace LiteNetLib
{
	//minimal hashset class from dotnet with some optimizations
	public partial class NetManager
	{
		private const int MaxPrimeArrayLength = 0x7FFFFFC3;
		private const int HashPrime = 101;
		private const int Lower31BitMask = 0x7FFFFFFF;
		private static readonly int[] Primes =
		{
			3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
			1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
			17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
			187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
			1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
		};

		private static int HashSetGetPrime(int min)
		{
			foreach (int prime in Primes)
			{
				if (prime >= min)
					return prime;
			}

			// Outside of our predefined table. Compute the hard way.
			for (int i = (min | 1); i < int.MaxValue; i += 2)
			{
				if (IsPrime(i) && ((i - 1) % HashPrime != 0))
					return i;
			}
			return min;

			bool IsPrime(int candidate)
			{
				if ((candidate & 1) != 0)
				{
					int limit = (int)Math.Sqrt(candidate);
					for (int divisor = 3; divisor <= limit; divisor += 2)
					{
						if (candidate % divisor == 0)
							return false;
					}
					return true;
				}
				return candidate == 2;
			}
		}

		private struct Slot
		{
			internal int HashCode;
			internal int Next;
			internal NetPeer Value;
		}

		private int[] _buckets;
		private Slot[] _slots;
		private int _count;
		private int _lastIndex;
		private int _freeList = -1;
		private NetPeer[] _peersArray = new NetPeer[32];
		private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
		private volatile NetPeer _headPeer;

		private void ClearPeerSet()
		{
			_peersLock.EnterWriteLock();
			_headPeer = null;
			if (_lastIndex > 0)
			{
				Array.Clear(_slots, 0, _lastIndex);
				Array.Clear(_buckets, 0, _buckets.Length);
				_lastIndex = 0;
				_count = 0;
				_freeList = -1;
			}
			_peersArray = new NetPeer[32];
			_peersLock.ExitWriteLock();
		}

		private bool ContainsPeer(NetPeer item)
		{
			if (item == null)
			{
				NetDebug.WriteError($"Contains peer null: {item}");
				return false;
			}
			if (_buckets != null)
			{
				int hashCode = item.GetHashCode() & Lower31BitMask;
				for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
				{
					if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(item))
						return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Gets peer by peer id
		/// </summary>
		/// <param name="id">id of peer</param>
		/// <returns>Peer if peer with id exist, otherwise null</returns>
		public NetPeer GetPeerById(int id)
		{
			return id >= 0 && id < _peersArray.Length ? _peersArray[id] : null;
		}

		/// <summary>
		/// Gets peer by peer id
		/// </summary>
		/// <param name="id">id of peer</param>
		/// <param name="peer">resulting peer</param>
		/// <returns>True if peer with id exist, otherwise false</returns>
		public bool TryGetPeerById(int id, out NetPeer peer)
		{
			peer = GetPeerById(id);
			return peer != null;
		}

		private void AddPeer(NetPeer peer)
		{
			if (peer == null)
			{
				NetDebug.WriteError($"Add peer null: {peer}");
				return;
			}
			_peersLock.EnterWriteLock();
			if (_headPeer != null)
			{
				peer.NextPeer = _headPeer;
				_headPeer.PrevPeer = peer;
			}
			_headPeer = peer;
			AddPeerToSet(peer);
			if (peer.Id >= _peersArray.Length)
			{
				int newSize = _peersArray.Length * 2;
				while (peer.Id >= newSize)
					newSize *= 2;
				Array.Resize(ref _peersArray, newSize);
			}
			_peersArray[peer.Id] = peer;
			_peersLock.ExitWriteLock();
		}

		private void RemovePeer(NetPeer peer, bool enableWriteLock)
		{
			if (enableWriteLock)
				_peersLock.EnterWriteLock();
			if (!RemovePeerFromSet(peer))
			{
				if (enableWriteLock)
					_peersLock.ExitWriteLock();
				return;
			}
			if (peer == _headPeer)
				_headPeer = peer.NextPeer;

			if (peer.PrevPeer != null)
				peer.PrevPeer.NextPeer = peer.NextPeer;
			if (peer.NextPeer != null)
				peer.NextPeer.PrevPeer = peer.PrevPeer;
			peer.PrevPeer = null;

			_peersArray[peer.Id] = null;
			_peerIds.Enqueue(peer.Id);

			if (enableWriteLock)
				_peersLock.ExitWriteLock();
		}

		private bool RemovePeerFromSet(NetPeer peer)
		{
			if (_buckets == null || peer == null)
				return false;
			int hashCode = peer.GetHashCode() & Lower31BitMask;
			int bucket = hashCode % _buckets.Length;
			int last = -1;
			for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].Next)
			{
				if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(peer))
				{
					if (last < 0)
						_buckets[bucket] = _slots[i].Next + 1;
					else
						_slots[last].Next = _slots[i].Next;
					_slots[i].HashCode = -1;
					_slots[i].Value = null;
					_slots[i].Next = _freeList;

					_count--;
					if (_count == 0)
					{
						_lastIndex = 0;
						_freeList = -1;
					}
					else
					{
						_freeList = i;
					}
					return true;
				}
			}
			return false;
		}

		private bool TryGetPeer(IPEndPoint endPoint, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
#if NET8_0_OR_GREATER
                //can be NetPeer or IPEndPoint
                int hashCode = (UseNativeSockets ? endPoint.GetHashCode() : endPoint.Serialize().GetHashCode()) & Lower31BitMask;
#else
				int hashCode = endPoint.GetHashCode() & Lower31BitMask;
#endif
				_peersLock.EnterReadLock();
				for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
				{
					if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(endPoint))
					{
						actualValue = _slots[i].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		//only used for NET8
		private bool TryGetPeer(SocketAddress saddr, out NetPeer actualValue)
		{
			if (_buckets != null)
			{
				int hashCode = saddr.GetHashCode() & Lower31BitMask;
				_peersLock.EnterReadLock();
				for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
				{
					if (_slots[i].HashCode == hashCode && _slots[i].Value.Serialize().Equals(saddr))
					{
						actualValue = _slots[i].Value;
						_peersLock.ExitReadLock();
						return true;
					}
				}
				_peersLock.ExitReadLock();
			}
			actualValue = null;
			return false;
		}

		private bool AddPeerToSet(NetPeer value)
		{
			if (_buckets == null)
			{
				int size = HashSetGetPrime(0);
				_buckets = new int[size];
				_slots = new Slot[size];
			}

			int hashCode = value.GetHashCode() & Lower31BitMask;
			int bucket = hashCode % _buckets.Length;
			for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
			{
				if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(value))
					return false;
			}

			int index;
			if (_freeList >= 0)
			{
				index = _freeList;
				_freeList = _slots[index].Next;
			}
			else
			{
				if (_lastIndex == _slots.Length)
				{
					//increase capacity
					int newSize = 2 * _count;
					newSize = (uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > _count
						? MaxPrimeArrayLength
						: HashSetGetPrime(newSize);

					// Able to increase capacity; copy elements to larger array and rehash
					Slot[] newSlots = new Slot[newSize];
					Array.Copy(_slots, 0, newSlots, 0, _lastIndex);
					_buckets = new int[newSize];
					for (int i = 0; i < _lastIndex; i++)
					{
						int b = newSlots[i].HashCode % newSize;
						newSlots[i].Next = _buckets[b] - 1;
						_buckets[b] = i + 1;
					}
					_slots = newSlots;
					// this will change during resize
					bucket = hashCode % _buckets.Length;
				}
				index = _lastIndex;
				_lastIndex++;
			}
			_slots[index].HashCode = hashCode;
			_slots[index].Value = value;
			_slots[index].Next = _buckets[bucket] - 1;
			_buckets[bucket] = index + 1;
			_count++;

			return true;
		}
	}

}